package vanilla_fss;
import java.util.Arrays;
import java.util.LinkedList;

import cec2010suite.F1;
import cec2010suite.F10;
import cec2010suite.F11;
import cec2010suite.F12;
import cec2010suite.F13;
import cec2010suite.F14;
import cec2010suite.F15;
import cec2010suite.F16;
import cec2010suite.F17;
import cec2010suite.F18;
import cec2010suite.F19;
import cec2010suite.F2;
import cec2010suite.F20;
import cec2010suite.F3;
import cec2010suite.F4;
import cec2010suite.F5;
import cec2010suite.F6;
import cec2010suite.F7;
import cec2010suite.F8;
import cec2010suite.F9;
import cec2010suite.Function;

/*
 * Copyright (c) 2011 Murilo Rebelo Pontes
 * murilo.pontes@gmail.com
 * 
 * GNU LESSER GENERAL PUBLIC LICENSE (Version 2.1, February 1999)
 */

public class FSS_AlgorithmMain {



	private static int school_individual_move(FSS_Fish[] school,Function f,FSS_PRNG r,double step_size){
		
		int count_success = 0;
		
		for(FSS_Fish fish: school){
			//use current as template for neighbor
			FSS_Solution.copy(fish.current, fish.neighbor);

			//clear displacement
			for(int i=0;i<f.getDimension();i++){
				fish.deltax[i]=0;
			}

			//not change all current variables at time
			int variables_to_change = r.nextInt(f.getDimension());

			//generate new solution in neighbor
			for(int i=0;i<variables_to_change;i++){
				//choose a variable
				int variable_idx = r.nextInt(f.getDimension());

				//bound box variables by Gaussian distribution
				//
				// |----------------------------------------------------------------------------------------|
				// |                                                                                        |
				// |  |-------------------------------------|                                               |
				// |  |                                     |    Gaussian bound box                         |
				// |  |              X <-- current solution | <--(avg=X std=part of search space)           | <-- search space bounds
				// |  |                                     |                                               |
				// |  |-------------------------------------|                                               |
				// |                                                                                        |
				// |                                                                                        |
				// |                                                                                        |
				// |----------------------------------------------------------------------------------------|
				//
				double avg=fish.neighbor.variables[variable_idx];
				double std=step_size*(f.getMax()-f.getMin());
				fish.neighbor.variables[variable_idx]=r.gaussian(avg, std);

				//take care about bounds of search space 
				if(fish.neighbor.variables[variable_idx]<f.getMin()) fish.neighbor.variables[variable_idx]=f.getMin();
				if(fish.neighbor.variables[variable_idx]>f.getMax()) fish.neighbor.variables[variable_idx]=f.getMax();

				//calculate displacement 
				fish.deltax[i] = fish.neighbor.variables[variable_idx] - fish.current.variables[variable_idx];
			}

			//evaluate new current solution
			fish.neighbor.fitness = f.compute(fish.neighbor.variables);

			//calculate fitness gain
			//on minimization (current - neighbor)
			//on maximization (neighbor - current)
			fish.fitness_gain = fish.current.fitness-fish.neighbor.fitness;
			//fish.fitness_gain = fish.neighbor.fitness-fish.current.fitness;


			//update current if neighbor is best
			fish.individual_move_success = false;
			if(fish.neighbor.fitness<fish.current.fitness){
				FSS_Solution.copy(fish.neighbor, fish.current);
				fish.individual_move_success = true;
				count_success++;
			}

			//if need replace best solution
			if(fish.current.fitness<fish.best.fitness){
				FSS_Solution.copy(fish.current, fish.best);
			}
		}
		return count_success;
	}

	private static void school_feeding(FSS_Fish[] school){

		//sort school by fitness gain
		Arrays.sort(school,new FSS_ComparatorByFitnessGain());

		//max absolute value of fitness gain
		double maxabs=Math.abs(school[0].fitness_gain);
		double maxabs2=Math.abs(school[school.length-1].fitness_gain);
		if(maxabs2>maxabs) maxabs=maxabs2;

		//calculate normalized gain
		for(FSS_Fish fish: school) fish.fitness_gain_normalized = fish.fitness_gain/maxabs;

		//feed all fishes
		for(FSS_Fish fish: school) {
			//
			fish.weight_past = fish.weight_now;
			fish.weight_now += fish.fitness_gain_normalized;

			//take care about min and max weight
			if(fish.weight_now<FSS_Parameters.fish_weight_min_running) fish.weight_now=FSS_Parameters.fish_weight_min_running; 
			if(fish.weight_now>FSS_Parameters.fish_weight_max_running) fish.weight_now=FSS_Parameters.fish_weight_max_running; 


		}


	}

	private static int school_colletive_instinctive_move(FSS_Fish[] school,Function f,double step_size){

		//
		double[] school_instinctive=new double[f.getDimension()];
		double[] sum_prod=new double[f.getDimension()];
		double sum_fitness_gain = 0;

		//clear
		for(int i=0;i<sum_prod.length;i++){
			sum_prod[i]=0;
			school_instinctive[i]=0;
		}

		//for each fish contribute with your direction scaled by your fitness gain
		for(FSS_Fish fish: school){
			//only good fishes
			if(fish.individual_move_success){
				//sum product of solution by fitness gain 
				for(int i=0;i<fish.deltax.length;i++){
					sum_prod[i]+= fish.deltax[i]*fish.fitness_gain_normalized;
				}
				//sum fitness gains
				sum_fitness_gain+=fish.fitness_gain_normalized;
			}
		}

		//calculate global direction of good fishes
		for(int i=0;i<sum_prod.length;i++){
			//take care about zero division
			if(sum_fitness_gain!=0){
				school_instinctive[i]=sum_prod[i]/sum_fitness_gain;
			} 
			else {
				school_instinctive[i]=0;
			}
		}

		//
		int count_success=0;
		for(FSS_Fish fish: school) {
			FSS_Solution.copy(fish.current, fish.neighbor);
			for(int i=0;i<fish.neighbor.variables.length;i++){
				fish.neighbor.variables[i] += step_size * school_instinctive[i];
				//take care about bounds of search space 
				if(fish.neighbor.variables[i]<f.getMin()) fish.neighbor.variables[i]=f.getMin();
				if(fish.neighbor.variables[i]>f.getMax()) fish.neighbor.variables[i]=f.getMax();
			}
			//evaluate new current solution
			fish.neighbor.fitness = f.compute(fish.neighbor.variables);

			//update current if neighbor is best
			fish.instinctive_move_success = false;
			if(fish.neighbor.fitness<fish.current.fitness){
				FSS_Solution.copy(fish.neighbor, fish.current);
				fish.instinctive_move_success = true;
				count_success++;
			}

			//if need replace best solution
			if(fish.current.fitness<fish.best.fitness){
				FSS_Solution.copy(fish.current, fish.best);
			}
		}

		return count_success;
	}

	private static int school_colletive_volitive_move(FSS_Fish[] school,Function f,double step_size,FSS_PRNG r){

		//
		double[] school_barycentre=new double[f.getDimension()];
		double[] sum_prod=new double[f.getDimension()];
		double sum_weight_now = 0;
		double sum_weight_past = 0;

		//clear
		for(int i=0;i<sum_prod.length;i++){
			sum_prod[i]=0;
			school_barycentre[i]=0;
		}

		//for each fish contribute with your position and weight
		for(FSS_Fish fish: school){
			for(int i=0;i<fish.deltax.length;i++){
				sum_prod[i]+= fish.deltax[i]*fish.weight_now;
			}
			//sum weight
			sum_weight_now+=fish.weight_now;
			sum_weight_past+=fish.weight_past;
		}
		//calculate barycentre
		for(int i=0;i<sum_prod.length;i++){
			school_barycentre[i]=sum_prod[i]/sum_weight_now;
		}

		double direction=0;
		if(sum_weight_now>sum_weight_past){
			//weight gain -> contract
			direction=1;
		} 
		else {
			//weight loss -> dilate
			direction=-1;
		}

		//
		int count_success=0;
		for(FSS_Fish fish: school) {
			FSS_Solution.copy(fish.current, fish.neighbor);
			for(int i=0;i<fish.neighbor.variables.length;i++){

				fish.neighbor.variables[i] += step_size * direction * r.between(0,1) * (fish.neighbor.variables[i]-school_barycentre[i]);

				//take care about bounds of search space 
				if(fish.neighbor.variables[i]<f.getMin()) fish.neighbor.variables[i]=f.getMin();
				if(fish.neighbor.variables[i]>f.getMax()) fish.neighbor.variables[i]=f.getMax();
			}
			//evaluate new current solution
			fish.neighbor.fitness = f.compute(fish.neighbor.variables);

			//update current if neighbor is best
			fish.volitive_move_success = false;
			if(fish.neighbor.fitness<fish.current.fitness){
				FSS_Solution.copy(fish.neighbor, fish.current);
				fish.volitive_move_success = true;
				count_success++;
			}

			//if need replace best solution
			if(fish.current.fitness<fish.best.fitness){
				FSS_Solution.copy(fish.current, fish.best);
			}
		}

		return count_success;
	}

	public static void optimize(Function f,long max_evaluations){

		//
		FSS_PRNG r = new FSS_PRNG();
		//
		long fitness_evaluations = 0;

		//
		FSS_AdaptiveStepSize step_individual = new FSS_AdaptiveStepSize(r);
		FSS_AdaptiveStepSize step_instinctive = new FSS_AdaptiveStepSize(r);
		FSS_AdaptiveStepSize step_volitive = new FSS_AdaptiveStepSize(r);

		//
		
		//
		boolean debug_init = false;
		boolean debug_iterate = false;

		//////////////////////////////////////////////////////////////////////////////////////
		// initialize school

		FSS_Fish[] school = new FSS_Fish[FSS_Parameters.school_size];
		for(int i=0;i<school.length;i++){
			school[i]=new FSS_Fish(f,r);	
		}
		//count one fitness evaluations per fish initialization in school
		fitness_evaluations+= school.length;

		//sort by best fitness
		Arrays.sort(school,new FSS_ComparatorByBestFitness());
		//print
		if(debug_init){
			for(FSS_Fish fish: school) {
				System.out.println("nei="+fish.neighbor.fitness+" now="+fish.current.fitness+" best="+fish.best.fitness);
			}
			System.out.printf("FE=%d fitness=%e\n",fitness_evaluations,school[0].best.fitness);
		}
		/////////////////////////////////////////////////////////////////////////////////////////


		//iterate 
		while(fitness_evaluations<=max_evaluations){

			////////////////////////////////////////////////////////////////////////////////////
			
			step_individual.success_past = step_individual.success_now;
			step_individual.success_now = school_individual_move(school,f,r,step_individual.stepsize);
			step_individual.adapt();

			//count one fitness evaluation per fish in individual move
			fitness_evaluations+= school.length;
			print_cec2010_checkpoint(fitness_evaluations, step_individual, step_instinctive,step_volitive, school);

			////////////////////////////////////////////////////////////////////////////////////

			school_feeding(school);

			/////////////////////////////////////////////////////////////////////////////////////

			step_instinctive.success_past = step_instinctive.success_now;
			step_instinctive.success_now = school_colletive_instinctive_move(school,f,step_instinctive.stepsize);
			step_instinctive.adapt();

			//count one fitness evaluation per fish in instinctive move
			fitness_evaluations+= school.length;
			print_cec2010_checkpoint(fitness_evaluations, step_individual, step_instinctive,step_volitive, school);

			/////////////////////////////////////////////////////////////////////////////////////

			step_volitive.success_past = step_volitive.success_now;
			step_volitive.success_now = school_colletive_volitive_move(school,f,step_volitive.stepsize,r);
			step_volitive.adapt();

			//count one fitness evaluation per fish in volitive move
			fitness_evaluations+= school.length;
			print_cec2010_checkpoint(fitness_evaluations, step_individual, step_instinctive,step_volitive, school);

			/////////////////////////////////////////////////////////////////////////////////////

			if(debug_iterate){
				Arrays.sort(school,new FSS_ComparatorByBestFitness());
				for(FSS_Fish fish: school) {
					System.out.println("nei="+fish.neighbor.fitness+" now="+fish.current.fitness+" best="+fish.best.fitness+" weight="+fish.weight_now);
				}
			}
			/////////////////////////////////////////////////////////////////////////////////////

		}


	}

	private static void print_cec2010_checkpoint(long fitness_evaluations,
			FSS_AdaptiveStepSize step_individual,
			FSS_AdaptiveStepSize step_instinctive,
			FSS_AdaptiveStepSize step_volitive, FSS_Fish[] school) {
		//CEC2010 checkpoints 1.2e5,6.0e5, 3.0e6
		if(
			fitness_evaluations == 120000  ||
			fitness_evaluations == 600000  ||
			fitness_evaluations == 3000000  
			//|| fitness_evaluations % 100000 == 0
		){
			Arrays.sort(school,new FSS_ComparatorByBestFitness());
			System.out.printf("FE=%d fitness=%e",fitness_evaluations,school[0].best.fitness);
			System.out.printf(" ind_sc=%02d",step_individual.success_now);
			System.out.printf(" ind_ss=%f",step_individual.stepsize);
			System.out.printf(" ins_sc=%02d",step_instinctive.success_now);
			System.out.printf(" ins_ss=%f",step_instinctive.stepsize);
			System.out.printf(" vol_sc=%2d",step_volitive.success_now);
			System.out.printf(" vol_ss=%f\n",step_volitive.stepsize);
		}
	}


	public static final void main(final String[] params) {
		LinkedList<Function> functions = new LinkedList<Function>();
		//
		functions.add(new F1());
		functions.add(new F2());
		functions.add(new F3());
		
		//
		functions.add(new F4());
		functions.add(new F5());
		functions.add(new F6());
		functions.add(new F7());
		functions.add(new F8());
		
		//
		functions.add(new F9());
		functions.add(new F10());
		functions.add(new F11());
		functions.add(new F12());
		functions.add(new F13());
		
		//
		functions.add(new F14());
		functions.add(new F15());
		functions.add(new F16());
		functions.add(new F17());
		functions.add(new F18());
		
		//
		functions.add(new F19());
		functions.add(new F20());

		for(Function fun: functions){
			System.out.println(fun.getShortName()+"/"+fun.getFullName()+" dimensions="+fun.getDimension()+" optimum="+fun.compute(fun.getOptimum()));
			FSS_AlgorithmMain.optimize(fun,(long)3e6);
			System.out.println();
		}
	}
}
